package com.wcs.exam.controller.auth.biz;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedissonConfigImpl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.wcs.exam.common.base.BaseBiz;
import com.wcs.exam.common.base.Result;
import com.wcs.exam.common.base.exception.BaseException;
import com.wcs.exam.common.base.page.Page;
import com.wcs.exam.common.base.page.PageUtil;
import com.wcs.exam.common.cache.CacheRedis;
import com.wcs.exam.common.config.ThreadContext;
import com.wcs.exam.common.constant.RedisConstant;
import com.wcs.exam.common.enums.*;
import com.wcs.exam.common.server.pay.GoodsInfo;
import com.wcs.exam.common.server.pay.PayFace;
import com.wcs.exam.common.server.pay.req.TradeCloseReq;
import com.wcs.exam.common.server.pay.req.TradeOrderReq;
import com.wcs.exam.common.server.pay.resp.TradeOrderResp;
import com.wcs.exam.common.server.pay.util.WxPayConfig;
import com.wcs.exam.common.util.BeanUtil;
import com.wcs.exam.common.util.PayUtil;
import com.wcs.exam.controller.auth.req.*;
import com.wcs.exam.controller.auth.resp.AuthOrderInfoGoodsResp;
import com.wcs.exam.controller.auth.resp.AuthOrderInfoPageResp;
import com.wcs.exam.controller.auth.resp.AuthOrderInfoPayResp;
import com.wcs.exam.controller.auth.resp.AuthOrderInfoResultResp;
import com.wcs.exam.controller.biz.OrderInfoCommonBiz;
import com.wcs.exam.controller.biz.SysConfigCommonBiz;
import com.wcs.exam.controller.biz.resp.SysConfigPayResp;
import com.wcs.exam.dao.*;
import com.wcs.exam.dao.impl.mapper.entity.*;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Auth-订单信息表
 *
 * @author wcs
 * @since 2024-07-06
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthOrderInfoBiz extends BaseBiz {

    @NotNull
    private final Map<String, PayFace> payFaceMap;

    @NotNull
    private final RedissonClient redissonClient;
    @NotNull
    private final CacheRedis cacheRedis;

    @NotNull
    private final SysConfigCommonBiz sysConfigCommonBiz;
    @NotNull
    private final OrderInfoCommonBiz orderInfoCommonBiz;

    @NotNull
    private final ExamDao examDao;
    @NotNull
    private final ExamUserDao examUserDao;

    @NotNull
    private final OrderInfoDao dao;
    @NotNull
    private final OrderPayDao orderPayDao;
    @NotNull
    private final UsersDao usersDao;

    /**
     * 获取商品是否购买状态
     *
     * @param userId    用户ID
     * @param goodsType 商品类型
     * @param goodsId   商品ID
     * @return 购买状态
     */
    private Boolean getGoodsPurchased(Long userId, Integer goodsType, Long goodsId) {
        if (GoodsTypeEnum.EXAM.getCode().equals(goodsType)) {
            ExamUser examUser = examUserDao.getByExamIdAndUserId(goodsId, userId);
            // 是否存在记录
            if (ObjectUtil.isNull(examUser)) {
                return Boolean.FALSE;
            }

            // 未付款
            if (Boolean.FALSE.equals(examUser.getPayment())) {
                return Boolean.FALSE;
            }

            // 是否长期有效
            if (Boolean.TRUE.equals(examUser.getLongEffective())) {
                return Boolean.TRUE;
            }
            // 是否已过期，当前时间小于结束时间
            return LocalDateTime.now().isBefore(examUser.getEndTime());
        }

        // 默认流程，返回已购买
        return Boolean.TRUE;
    }

    /**
     * 订单信息-支付
     *
     * @param req 请求参数
     * @return 响应结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<AuthOrderInfoPayResp> pay(AuthOrderInfoPayReq req) {
        // 校验下单参数
        GoodsTypeEnum goodsTypeEnum = GoodsTypeEnum.byCode(req.getGoodsType());
        if (ObjectUtil.isNull(goodsTypeEnum)) {
            return Result.error("商品类型错误");
        }
        PayTypeEnum payTypeEnum = PayTypeEnum.byCode(req.getPayType());
        if (ObjectUtil.isNull(payTypeEnum)) {
            return Result.error("支付方式错误");
        }
        OrderSourceEnum orderSourceEnum = OrderSourceEnum.byCode(req.getOrderSource());
        if (ObjectUtil.isNull(orderSourceEnum)) {
            return Result.error("订单来源错误");
        }

        // 支付配置
        SysConfigPayResp configPay = sysConfigCommonBiz.getPay();
        if (ObjectUtil.isNull(configPay)) {
            return Result.error("支付配置不存在");
        }

        // 校验openId
        if (PayTypeEnum.WX_MP_PAY.equals(payTypeEnum)) {
            if (StrUtil.isBlank(req.getOpenId())) {
                return Result.error("OpenId不能为空");
            }
        } else if (PayTypeEnum.WX_MINI_APP_PAY.equals(payTypeEnum)) {
            if (StrUtil.isBlank(req.getOpenId())) {
                if (StrUtil.isBlank(req.getCode())) {
                    return Result.error("授权code不能为空");
                }

                // 获取openID
                String openId = getWxMiniAppOpenId(configPay, req.getCode());
                if (StrUtil.isBlank(openId)) {
                    return Result.error("授权Code获取授权信息信息失败");
                }
                req.setOpenId(openId);
            }
        }

        // 用户信息
        Users user = usersDao.getById(ThreadContext.getUserId());
        if (ObjectUtil.isNull(user)) {
            return Result.error("用户信息不存在");
        }

        // 获取商品信息
        GoodsInfo goodsInfo = getGoodsInfo(req.getGoodsType(), req.getGoodsId());
        if (ObjectUtil.isNull(goodsInfo)) {
            return Result.error("商品不存在");
        }


        // 针对用户进行加锁
        RLock lock = redissonClient.getLock(RedisConstant.Common.ORDER_INFO + ThreadContext.getUserId());
        try {
            boolean lockResult = lock.tryLock(RedisConstant.DEFAULT_WAIT_TIME, RedisConstant.DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
            if (!lockResult) {
                throw new BaseException("等待超时");
            }

            // 若存在待支付订单或交易记录，则关闭
            handleGoodsOrderClose(req, configPay);

            // 创建交易订单和交易记录，并且调用支付接口下单
            return handleOrder(user, goodsInfo, req, payTypeEnum, configPay);
        } catch (BaseException e) {
            log.error("交易下单失败，用户ID：{}--下单参数：{}", ThreadContext.getUserId(), JSONUtil.toJsonStr(req), e);
            Thread.currentThread().interrupt();
            throw new BaseException(e.getMessage());
        } catch (Exception e) {
            log.error("交易下单失败，用户ID：{}--下单参数：{}", ThreadContext.getUserId(), JSONUtil.toJsonStr(req), e);
            Thread.currentThread().interrupt();
            throw new BaseException(toI18nFail());
        } finally {
            // 释放锁
            lock.unlockAsync();
        }
    }

    /**
     * 获取微信小程序对应OpenId
     *
     * @param configPay 店铺配置
     * @param code      授权Code
     * @return OpenId
     */
    private String getWxMiniAppOpenId(SysConfigPayResp configPay, String code) {
        try {
            WxPayConfig wxPayConfig = configPay.getWxPayConfig();
            String cacheKey = RedisConstant.Other.WX_MINI_APP_AUTH.concat(wxPayConfig.getWxMiniAppId()).concat(RedisConstant.SEPARATOR).concat(code);
            WxMaJscode2SessionResult sessionResult = cacheRedis.get(cacheKey, WxMaJscode2SessionResult.class);
            if (ObjectUtil.isNotNull(sessionResult)) {
                return sessionResult.getOpenid();
            }

            WxMaService wxMaService = new WxMaServiceImpl();
            WxMaRedissonConfigImpl wxMaConfig = new WxMaRedissonConfigImpl(redissonClient);
            wxMaConfig.setAppid(wxPayConfig.getWxMiniAppId());
            wxMaConfig.setSecret(wxPayConfig.getWxMiniAppSecret());
            wxMaService.setWxMaConfig(wxMaConfig);

            sessionResult = wxMaService.jsCode2SessionInfo(code);
            if (ObjectUtil.isNotNull(sessionResult)) {
                cacheRedis.set(cacheKey, sessionResult, 2, TimeUnit.HOURS);
                return sessionResult.getOpenid();
            }
            return null;
        } catch (WxErrorException e) {
            log.error("微信小程序，获取授权信息失败", e);
            throw new BaseException("授权Code获取授权信息信息失败");
        }
    }

    private void handleGoodsOrderClose(AuthOrderInfoPayReq req, SysConfigPayResp configPay) {
        // 查询待支付的交易记录（需要关闭待支付的交易记录，防止重复支付）
        LambdaQueryWrapper<OrderPay> wrapper = new LambdaQueryWrapper<OrderPay>()
                .eq(OrderPay::getUserId, ThreadContext.getUserId())
                .eq(OrderPay::getGoodsType, req.getGoodsType())
                .eq(OrderPay::getGoodsId, req.getGoodsId())
                .eq(OrderPay::getOrderStatus, OrderStatusEnum.WAIT.getCode());
        List<OrderPay> tradeRecordList = orderPayDao.list(wrapper);
        if (CollectionUtil.isEmpty(tradeRecordList)) {
            return;
        }

        // 调用支付通道进行订单关闭（调用支付通道关闭，防止出现类似手机扫码后，然后在支付软件中直接支付导致重复支付的情况）
        for (OrderPay orderPay : tradeRecordList) {
            closeTradeRecord(configPay, orderPay);
        }

        // 关闭待支付的交易记录
        OrderPay tradeRecord = new OrderPay();
        tradeRecord.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
        orderPayDao.update(tradeRecord, wrapper);

        // 关闭交易订单
        dao.lambdaUpdate()
                .eq(OrderInfo::getUserId, ThreadContext.getUserId())
                .eq(OrderInfo::getGoodsType, req.getGoodsType())
                .eq(OrderInfo::getGoodsId, req.getGoodsId())
                .eq(OrderInfo::getOrderStatus, OrderStatusEnum.WAIT.getCode())
                .set(OrderInfo::getOrderStatus, OrderStatusEnum.CLOSE.getCode())
                .update();
    }

    /**
     * 关闭交易订单
     *
     * @param configPay 店铺配置
     * @param orderPay  校验记录
     */
    private void closeTradeRecord(SysConfigPayResp configPay, OrderPay orderPay) {
        // 获取支付接口
        PayTypeEnum payTypeEnum = PayTypeEnum.byCode(orderPay.getPayType());
        if (ObjectUtil.isNull(payTypeEnum)) {
            return;
        }

        PayFace payFace = payFaceMap.get(payTypeEnum.getPayImpl());
        if (ObjectUtil.isNull(payFace)) {
            return;
        }

        // 处理交易关闭参数
        TradeCloseReq tradeCloseReq = new TradeCloseReq()
                .setOrderNo(String.valueOf(orderPay.getId()));
        // 校验并且获取支付配置
        try {
            if (PayWayEnum.ALI_PAY.getCode().equals(orderPay.getPayWay())) {
                // 获取支付宝配置
                tradeCloseReq.setAliPayConfig(configPay.getAliPayConfig());
            } else if (PayWayEnum.WX_PAY.getCode().equals(orderPay.getPayWay())) {
                // 获取微信支付配置
                tradeCloseReq.setWxPayConfig(configPay.getWxPayConfig());
            }
        } catch (Exception e) {
            log.error("关闭交易记录获取支付配置失败", e);
            return;
        }

        // 调用接口，关闭交易记录
        payFace.tradeClose(tradeCloseReq);
    }


    /**
     * 处理交易下单
     *
     * @param user        用户信息
     * @param goodsInfo   商品信息
     * @param req         下单参数
     * @param payTypeEnum 支付类型
     * @param configPay   店铺配置
     * @return 处理结果（返回支付信息）
     */
    private Result<AuthOrderInfoPayResp> handleOrder(Users user, GoodsInfo goodsInfo, AuthOrderInfoPayReq req, PayTypeEnum payTypeEnum, SysConfigPayResp configPay) {
        // 设置会员折扣
        if (goodsInfo.getPaidAmount().compareTo(BigDecimal.ZERO) == 0) {
            // 免费，直接支付完成
            orderInfoCommonBiz.directSuccessHandle(user.getId(), goodsInfo.getGoodsId(), goodsInfo.getGoodsType());

            AuthOrderInfoPayResp resp = new AuthOrderInfoPayResp();
            resp.setOrderStatus(OrderStatusEnum.SUCCESS.getCode());
            log.info("响应支付信息：{}", JSONUtil.toJsonStr(resp));
            return Result.success(resp);
        }

        // 创建支付订单
        OrderInfo orderInfo = createOrderInfo(user, goodsInfo, payTypeEnum, req);
        dao.save(orderInfo);

        // 创建支付记录
        OrderPay orderPay = createTradePay(payTypeEnum, orderInfo);
        orderPayDao.save(orderPay);

        // 响应信息
        AuthOrderInfoPayResp resp = new AuthOrderInfoPayResp()
                .setTradeSerialNo(orderPay.getId())
                .setLinePrice(orderPay.getLinePrice())
                .setOrderAmount(orderPay.getOrderAmount())
                .setPaidAmount(orderPay.getPaidAmount())
                .setRemarkCus(orderPay.getRemarkCus())
                .setOrderStatus(OrderStatusEnum.WAIT.getCode());

        // 获取支付通道
        PayFace payFace = payFaceMap.get(payTypeEnum.getPayImpl());
        if (ObjectUtil.isNull(payFace)) {
            throw new BaseException("获取支付通道失败，支付通道不存在");
        }

        // 处理请求参数
        String notifyUrl = PayUtil.getShopNotifyUrl(configPay.getWebsiteDomain(), payTypeEnum.getPayImpl());
        String tradeSerialNo = String.valueOf(orderPay.getId());
        // 创建下单请求参数
        TradeOrderReq tradeOrderReq = new TradeOrderReq()
                .setTradeSerialNo(tradeSerialNo)
                .setAmount(orderPay.getPaidAmount())
                .setGoodsName(orderPay.getGoodsName())
                .setUserClientIp(req.getUserClientIp())
                .setTimeExpire(Date.from(orderInfo.getExpiredTime().atZone(ZoneId.systemDefault()).toInstant()))
                .setNotifyUrl(notifyUrl)
                .setQuitUrl(req.getRedirectUrl() + "?tradeSerialNo=" + tradeSerialNo);

        if (PayTypeEnum.WX_MP_PAY.equals(payTypeEnum) || PayTypeEnum.WX_MINI_APP_PAY.equals(payTypeEnum)) {
            tradeOrderReq.setOpenId(req.getOpenId());
        }

        // 校验并且获取支付配置（当返回信息不为空时，表示配置有误）
        if (PayWayEnum.ALI_PAY.equals(payTypeEnum.getPayWay())) {
            // 支付宝支付
            tradeOrderReq.setAliPayConfig(configPay.getAliPayConfig());
        } else if (PayWayEnum.WX_PAY.equals(payTypeEnum.getPayWay())) {
            // 微信支付
            tradeOrderReq.setWxPayConfig(configPay.getWxPayConfig());
        }

        // 交易下单
        TradeOrderResp tradeOrderResp = payFace.tradeOrder(tradeOrderReq);
        if (ObjectUtil.isNull(tradeOrderResp) || !tradeOrderResp.isSuccess()) {
            log.warn("交易下单失败，下单返回信息：{}", JSONUtil.toJsonStr(tradeOrderResp));
            return Result.error("交易下单失败");
        }

        // 处理返回信息
        resp.setPayMessage(tradeOrderResp.getPayMessage());

        log.info("响应支付信息：{}", JSONUtil.toJsonStr(resp));
        return Result.success(resp);
    }

    /**
     * 创建交易订单
     *
     * @param users       用户信息
     * @param goodsInfo   商品信息
     * @param payTypeEnum 支付类型枚举
     * @param req         下单请求参数
     * @return 交易订单
     */
    private OrderInfo createOrderInfo(Users users, GoodsInfo goodsInfo, PayTypeEnum payTypeEnum, AuthOrderInfoPayReq req) {
        return new OrderInfo()
                .setOrderNo(generateOrderNo())
                .setUserId(users.getId())
                .setNickname(users.getNickname())
                .setMobile(users.getMobile())
                .setRegisterTime(users.getGmtCreate())
                .setGoodsType(goodsInfo.getGoodsType())
                .setGoodsId(goodsInfo.getGoodsId())
                .setGoodsName(goodsInfo.getGoodsName())
                .setLinePrice(goodsInfo.getLinePrice())
                .setOrderAmount(goodsInfo.getOrderAmount())
                .setPaidAmount(goodsInfo.getPaidAmount())
                .setExpiredTime(LocalDateTime.now().plusMinutes(30))
                .setChangePrice(Boolean.FALSE)
                .setOrderStatus(OrderStatusEnum.WAIT.getCode())
                .setPayWay(payTypeEnum.getPayWay().getCode())
                .setPayType(payTypeEnum.getCode())
                .setRemarkCus(req.getRemarkCus());
    }

    /**
     * 创建交易记录
     *
     * @param payTypeEnum 支付类型枚举
     * @param tradeOrder  交易订单
     * @return 交易记录
     */
    private OrderPay createTradePay(PayTypeEnum payTypeEnum, OrderInfo tradeOrder) {
        return new OrderPay()
                .setOrderNo(tradeOrder.getOrderNo())
                .setSerialNumber(IdWorker.getId())
                .setUserId(tradeOrder.getUserId())
                .setGoodsType(tradeOrder.getGoodsType())
                .setGoodsId(tradeOrder.getGoodsId())
                .setGoodsName(tradeOrder.getGoodsName())
                .setLinePrice(tradeOrder.getLinePrice())
                .setPaidAmount(tradeOrder.getPaidAmount())
                .setOrderAmount(tradeOrder.getOrderAmount())
                .setOrderStatus(tradeOrder.getOrderStatus())
                .setPayWay(payTypeEnum.getPayWay().getCode())
                .setPayType(payTypeEnum.getCode())
                .setRemarkCus(tradeOrder.getRemarkCus());
    }

    /**
     * 创建订单号
     *
     * @return 订单号
     */
    private String generateOrderNo() {
        // 创建订单号前缀（10位）
        Date date = new Date();
        String prefix = DateUtil.format(date, "yyMMddHHmm");

        // 获取AtomicLong对象
        RAtomicLong rAtomicLong = redissonClient.getAtomicLong(RedisConstant.Other.ORDER_NO + prefix);
        rAtomicLong.expire(DateUtil.offsetMinute(date, 2).toInstant());

        // 获取4位随机数
        long count = rAtomicLong.incrementAndGet();
        String content = String.format("%04d", count);
        return prefix + content;
    }

    /**
     * 获取商品信息
     *
     * @param goodsType 商品类型
     * @param goodsId   商品ID
     * @return 商品信息
     */
    private GoodsInfo getGoodsInfo(Integer goodsType, Long goodsId) {
        if (GoodsTypeEnum.EXAM.getCode().equals(goodsType)) {
            // 考试信息
            return getExamInfo(goodsType, goodsId);
        } else {
            // 其他类型暂不支持购买
            throw new BaseException("该商品类型暂不支持");
        }
    }

    private GoodsInfo getExamInfo(Integer goodsType, Long examId) {
        // 获取商品
        Exam exam = examDao.getById(examId);
        if (ObjectUtil.isNull(exam)) {
            throw new BaseException("考试不存在");
        }

        // 校验商品状态
        if (!PublishStatusEnum.PUBLISHED.getCode().equals(exam.getPublishStatus())) {
            throw new BaseException("考试未发布");
        }

        // 校验商品价格
        if (BigDecimal.ZERO.compareTo(exam.getPrice()) >= 0) {
            throw new BaseException("免费商品不支持购买");
        }

        // 判断是否已经购买
        ExamUser examUser = examUserDao.getByExamTypeAndExamIdAndUserId(exam.getExamType(), exam.getId(), ThreadContext.getUserId());
        if (ObjectUtil.isNotNull(examUser) && Boolean.TRUE.equals(examUser.getPayment())) {
            // 课程存在
            // 是否长期有效
            if (Boolean.TRUE.equals(examUser.getLongEffective())) {
                // 长期有效
                throw new BaseException("该商品已经购买，无需重复购买");
            } else {
                // 非长期有效，校验是否已经过期(当前时间在结束时间之前)
                if (LocalDateTime.now().isBefore(examUser.getEndTime())) {
                    throw new BaseException("该商品已经购买，无需重复购买");
                }
            }
        }

        // 返回商品信息
        GoodsInfo goodsInfo = new GoodsInfo();
        goodsInfo.setGoodsType(goodsType);
        goodsInfo.setGoodsId(examId);
        goodsInfo.setGoodsName(exam.getExamName());
        // 订单价格
        goodsInfo.setLinePrice(exam.getLinePrice());
        goodsInfo.setOrderAmount(exam.getPrice());
        // 优惠价格
        // 真实支付金额（扣减完支付活动折扣、优惠券、优惠码等等后，用户需要支付的价格）
        goodsInfo.setPaidAmount(exam.getPrice());
        return goodsInfo;
    }

    /**
     * 订单信息-继续支付
     *
     * @param req 请求参数
     * @return 响应结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<AuthOrderInfoPayResp> continuePay(AuthOrderInfoContinuePayReq req) {
        // 校验下单参数
        PayTypeEnum payTypeEnum = PayTypeEnum.byCode(req.getPayType());
        if (ObjectUtil.isNull(payTypeEnum)) {
            return Result.error("支付方式错误");
        }

        // 获取店铺配置
        SysConfigPayResp configPay = sysConfigCommonBiz.getPay();
        if (ObjectUtil.isNull(configPay)) {
            return Result.error("支付配置不存在");
        }

        // 校验openId
        if (PayTypeEnum.WX_MP_PAY.equals(payTypeEnum)) {
            if (StrUtil.isBlank(req.getOpenId())) {
                return Result.error("OpenId不能为空");
            }
        } else if (PayTypeEnum.WX_MINI_APP_PAY.equals(payTypeEnum)) {
            if (StrUtil.isBlank(req.getOpenId())) {
                if (StrUtil.isBlank(req.getCode())) {
                    return Result.error("授权code不能为空");
                }

                // 获取openID
                String openId = getWxMiniAppOpenId(configPay, req.getCode());
                if (StrUtil.isBlank(openId)) {
                    return Result.error("授权Code获取授权信息信息失败");
                }
                req.setOpenId(openId);
            }
        }

        // 获取用户信息
        Users user = usersDao.getById(ThreadContext.getUserId());
        if (ObjectUtil.isNull(user)) {
            return Result.error("用户信息不存在");
        }

        // 针对用户进行加锁
        RLock lock = redissonClient.getLock(RedisConstant.Common.ORDER_INFO + ThreadContext.getUserId());
        try {
            boolean lockResult = lock.tryLock(RedisConstant.DEFAULT_WAIT_TIME, RedisConstant.DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
            if (!lockResult) {
                throw new BaseException("等待超时");
            }

            // 校验交易订单
            OrderInfo orderInfo = dao.getByOrderNo(req.getOrderNo());
            if (ObjectUtil.isNull(orderInfo) || !ThreadContext.getUserId().equals(orderInfo.getUserId())) {
                return Result.error("订单信息不存在");
            }
            if (!OrderStatusEnum.WAIT.getCode().equals(orderInfo.getOrderStatus())) {
                return Result.error("该订单已经结束，不能继续支付");
            }

            // 兼用处理超时未及时关闭的订单
            if (!orderInfo.getExpiredTime().isAfter(LocalDateTime.now())) {
                // 关闭交易订单
                OrderInfo updateOrderInfo = new OrderInfo();
                updateOrderInfo.setId(orderInfo.getId());
                updateOrderInfo.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
                dao.updateById(updateOrderInfo);

                // 关闭交易记录
                orderPayDao.lambdaUpdate()
                        .eq(OrderPay::getOrderNo, orderInfo.getOrderNo())
                        .eq(OrderPay::getOrderStatus, OrderStatusEnum.WAIT.getCode())
                        .set(OrderPay::getOrderStatus, OrderStatusEnum.CLOSE.getCode());
                return Result.error("该订单已经结束，不能继续支付");
            }

            // 获取商品信息
            GoodsInfo goodsInfo = getGoodsInfo(orderInfo.getGoodsType(), orderInfo.getGoodsId());
            if (ObjectUtil.isNull(goodsInfo)) {
                return Result.error("商品不存在");
            }
            if (Boolean.TRUE.equals(orderInfo.getChangePrice())) {
                // 改价后(改价后以订单价格为准，不在更新订单价格)
                goodsInfo.setPaidAmount(orderInfo.getPaidAmount());
            }

            // 关闭交易记录
            handleContinuePayTradeRecordClose(orderInfo, configPay);

            // 处理继续支付
            return handleContinuePay(orderInfo, goodsInfo, req, payTypeEnum, configPay);
        } catch (InterruptedException e) {
            log.error("继续支付失败，用户ID：{}--支付参数：{}", ThreadContext.getUserId(), JSONUtil.toJsonStr(req));
            Thread.currentThread().interrupt();
            throw new BaseException(toI18nFail());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 处理继续支付-交易记录关闭
     *
     * @param tradeOrder 交易订单
     * @param configPay  店铺配置
     */
    private void handleContinuePayTradeRecordClose(OrderInfo tradeOrder, SysConfigPayResp configPay) {
        // 1：获取待支付交易记录（需要关闭待支付的交易记录，防止重复支付）
        LambdaQueryWrapper<OrderPay> wrapper = new LambdaQueryWrapper<OrderPay>()
                .eq(OrderPay::getOrderNo, tradeOrder.getOrderNo())
                .eq(OrderPay::getOrderStatus, OrderStatusEnum.WAIT.getCode());
        List<OrderPay> orderPayList = orderPayDao.list(wrapper);
        if (CollectionUtil.isEmpty(orderPayList)) {
            return;
        }

        // 调用支付通道进行订单关闭（调用支付通道关闭，防止出现类似手机扫码后，然后在支付软件中直接支付导致重复支付的情况）
        for (OrderPay orderPay : orderPayList) {
            closeTradeRecord(configPay, orderPay);
        }

        // 关闭待支付的交易记录
        OrderPay tradeRecord = new OrderPay();
        tradeRecord.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
        orderPayDao.update(tradeRecord, wrapper);
    }

    /**
     * 处理继续支付
     *
     * @param orderInfo   交易订单
     * @param goodsInfo   商品信息
     * @param req         继续支付参数
     * @param payTypeEnum 支付方式
     * @param configPay   店铺配置
     * @return 处理结果（返回支付信息）
     */
    private Result<AuthOrderInfoPayResp> handleContinuePay(OrderInfo orderInfo, GoodsInfo goodsInfo, AuthOrderInfoContinuePayReq req, PayTypeEnum payTypeEnum, SysConfigPayResp configPay) {
        // 设置会员折扣价
        if (goodsInfo.getPaidAmount().compareTo(BigDecimal.ZERO) == 0) {
            // 免费，直接支付完成
            orderInfoCommonBiz.directSuccessHandle(orderInfo.getUserId(), goodsInfo.getGoodsId(), goodsInfo.getGoodsType());

            AuthOrderInfoPayResp resp = new AuthOrderInfoPayResp();
            resp.setOrderStatus(OrderStatusEnum.SUCCESS.getCode());
            log.info("响应支付信息：{}", JSONUtil.toJsonStr(resp));
            return Result.success(resp);
        }

        // 更新交易订单信息
        OrderInfo updateTradeOrder = createContinuePayTradeOrder(orderInfo, goodsInfo, payTypeEnum);
        dao.updateById(updateTradeOrder);

        // 创建支付记录
        OrderPay orderPay = createContinuePayTradeRecord(orderInfo, updateTradeOrder, payTypeEnum);
        orderPayDao.save(orderPay);

        // 处理返回信息
        AuthOrderInfoPayResp resp = new AuthOrderInfoPayResp()
                .setTradeSerialNo(orderPay.getId())
                .setLinePrice(orderPay.getLinePrice())
                .setOrderAmount(orderPay.getOrderAmount())
                .setPaidAmount(orderPay.getPaidAmount())
                .setRemarkCus(orderPay.getRemarkCus())
                .setOrderStatus(OrderStatusEnum.WAIT.getCode());

        // 获取支付通道
        PayFace payFace = payFaceMap.get(payTypeEnum.getPayImpl());
        if (ObjectUtil.isNull(payFace)) {
            throw new BaseException("获取支付通道失败，支付通道不存在");
        }

        // 处理请求参数
        String notifyUrl = PayUtil.getShopNotifyUrl(configPay.getWebsiteDomain(), payTypeEnum.getPayImpl());
        String tradeSerialNo = String.valueOf(orderPay.getId());
        // 创建下单请求参数
        TradeOrderReq tradeOrderReq = new TradeOrderReq()
                .setTradeSerialNo(tradeSerialNo)
                .setAmount(orderPay.getPaidAmount())
                .setGoodsName(orderPay.getGoodsName())
                .setUserClientIp(req.getUserClientIp())
                .setTimeExpire(Date.from(orderInfo.getExpiredTime().atZone(ZoneId.systemDefault()).toInstant()))
                .setNotifyUrl(notifyUrl)
                .setQuitUrl(req.getRedirectUrl() + "?tradeSerialNo=" + tradeSerialNo);
        if (PayTypeEnum.WX_MP_PAY.equals(payTypeEnum) || PayTypeEnum.WX_MINI_APP_PAY.equals(payTypeEnum)) {
            tradeOrderReq.setOpenId(req.getOpenId());
        }

        // 校验并且获取支付配置（当返回信息不为空时，表示配置有误）
        if (PayWayEnum.ALI_PAY.equals(payTypeEnum.getPayWay())) {
            // 支付宝支付
            tradeOrderReq.setAliPayConfig(configPay.getAliPayConfig());
        } else {
            // 微信支付
            tradeOrderReq.setWxPayConfig(configPay.getWxPayConfig());
        }

        // 交易下单
        TradeOrderResp tradeOrderResp = payFace.tradeOrder(tradeOrderReq);
        if (ObjectUtil.isNull(tradeOrderResp) || !tradeOrderResp.isSuccess()) {
            log.warn("交易下单失败，下单返回信息：{}", JSONUtil.toJsonStr(tradeOrderResp));
            return Result.error("交易下单失败");
        }

        // 处理返回信息
        resp.setPayMessage(tradeOrderResp.getPayMessage());
        return Result.success(resp);
    }

    /**
     * 继续支付--创建待更新交易订单
     *
     * @param orderInfo   交易订单
     * @param goodsInfo   商品信息
     * @param payTypeEnum 支付类型枚举
     * @return 待更新交易订单
     */
    private OrderInfo createContinuePayTradeOrder(OrderInfo orderInfo, GoodsInfo goodsInfo, PayTypeEnum payTypeEnum) {
        LocalDateTime expiredTime;
        // 订单过期时间设置为30分钟
        expiredTime = orderInfo.getGmtCreate().plusMinutes(30);

        OrderInfo updateOrderInfo = new OrderInfo();
        updateOrderInfo.setId(orderInfo.getId());
        updateOrderInfo.setGoodsType(goodsInfo.getGoodsType());
        updateOrderInfo.setGoodsId(goodsInfo.getGoodsId());
        updateOrderInfo.setGoodsName(goodsInfo.getGoodsName());
        updateOrderInfo.setLinePrice(goodsInfo.getLinePrice());
        updateOrderInfo.setPaidAmount(goodsInfo.getPaidAmount());
        updateOrderInfo.setOrderAmount(goodsInfo.getOrderAmount());
        updateOrderInfo.setExpiredTime(expiredTime.isAfter(LocalDateTime.now()) ? expiredTime : LocalDateTime.now().plusMinutes(30));
        updateOrderInfo.setPayWay(payTypeEnum.getPayWay().getCode());
        updateOrderInfo.setPayType(payTypeEnum.getCode());
        return updateOrderInfo;
    }

    /**
     * 继续支付-创建交易记录
     *
     * @param orderInfo       原交易订单
     * @param updateOrderInfo 待更新交易订单
     * @param payTypeEnum     支付类型枚举
     * @return 交易记录
     */
    private OrderPay createContinuePayTradeRecord(OrderInfo orderInfo, OrderInfo updateOrderInfo, PayTypeEnum payTypeEnum) {
        return new OrderPay()
                .setOrderNo(orderInfo.getOrderNo())
                .setSerialNumber(IdWorker.getId())
                .setUserId(orderInfo.getUserId())
                .setGoodsType(updateOrderInfo.getGoodsType())
                .setGoodsId(updateOrderInfo.getGoodsId())
                .setGoodsName(updateOrderInfo.getGoodsName())
                .setLinePrice(updateOrderInfo.getLinePrice())
                .setPaidAmount(updateOrderInfo.getPaidAmount())
                .setOrderAmount(updateOrderInfo.getOrderAmount())
                .setOrderStatus(orderInfo.getOrderStatus())
                .setPayWay(payTypeEnum.getPayWay().getCode())
                .setPayType(payTypeEnum.getCode())
                .setRemarkCus(orderInfo.getRemarkCus());
    }

    /**
     * 订单信息-取消订单
     *
     * @param req 订单关闭参数
     * @return 关闭结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<String> cancel(AuthOrderInfoCancelReq req) {
        // 校验交易订单
        OrderInfo orderInfo = dao.getByOrderNo(req.getOrderNo());
        if (ObjectUtil.isNull(orderInfo) || !ThreadContext.getUserId().equals(orderInfo.getUserId())) {
            return Result.error("订单信息不存在");
        }
        if (!OrderStatusEnum.WAIT.getCode().equals(orderInfo.getOrderStatus())) {
            return Result.error("该订单已经结束，不能继续支付");
        }

        // 获取待支付交易记录（需要关闭待支付的交易记录，防止重复支付）
        LambdaQueryWrapper<OrderPay> wrapper = new LambdaQueryWrapper<OrderPay>()
                .eq(OrderPay::getOrderNo, orderInfo.getOrderNo())
                .eq(OrderPay::getOrderStatus, OrderStatusEnum.WAIT.getCode());
        List<OrderPay> orderPayList = orderPayDao.list(wrapper);
        if (CollectionUtil.isNotEmpty(orderPayList)) {

            // 查询交易记录的所有店铺配置
            SysConfigPayResp configPay = sysConfigCommonBiz.getPay();
            if (ObjectUtil.isNull(configPay)) {
                return Result.error("支付配置不存在");
            }

            // 调用支付通道进行订单关闭（调用支付通道关闭，防止出现类似手机扫码后，然后在支付软件中直接支付导致重复支付的情况）
            for (OrderPay tradeRecord : orderPayList) {
                closeTradeRecord(configPay, tradeRecord);
            }

            // 关闭待支付的交易记录
            OrderPay tradeRecord = new OrderPay();
            tradeRecord.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
            orderPayDao.update(tradeRecord, wrapper);
        }

        // 关闭待支付的交易订单
        OrderInfo updateOrderInfo = new OrderInfo();
        updateOrderInfo.setId(orderInfo.getId());
        updateOrderInfo.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
        if (dao.updateById(updateOrderInfo)) {
            return Result.success(toI18nSuccess());
        }
        return Result.error(toI18nFail());
    }

    /**
     * 商品信息查看
     *
     * @param req 请求参数
     * @return 商品信息
     */
    public Result<AuthOrderInfoGoodsResp> view(AuthOrderInfoGoodsReq req) {
        if (GoodsTypeEnum.EXAM.getCode().equals(req.getGoodsType())) {
            Exam exam = examDao.getById(req.getGoodsId());
            if (ObjectUtil.isNull(exam)) {
                return Result.error("考试信息不存在");
            }
            Boolean purchased = getGoodsPurchased(ThreadContext.getUserId(), req.getGoodsType(), req.getGoodsId());

            return Result.success(new AuthOrderInfoGoodsResp()
                    .setGoodsId(exam.getId())
                    .setGoodsName(exam.getExamName())
                    .setGoodsType(req.getGoodsType())
                    .setGoodsValid(PublishStatusEnum.PUBLISHED.getCode().equals(exam.getPublishStatus()))
                    .setLinePrice(exam.getLinePrice())
                    .setPrice(exam.getPrice())
                    .setPurchased(purchased));
        } else {
            return Result.error("该商品类型暂不支持");
        }
    }

    /**
     * 订单信息查看-支付结果查询
     *
     * @param tradeSerialNo 岛屿流水号
     * @return 交易流水号
     */
    public Result<AuthOrderInfoResultResp> result(Long tradeSerialNo) {
        OrderPay orderPay = orderPayDao.getById(tradeSerialNo);
        if (ObjectUtil.isNull(orderPay) || !ThreadContext.getUserId().equals(orderPay.getUserId())) {
            return Result.error("订单信息不存在");
        }
        AuthOrderInfoResultResp resp = BeanUtil.copyProperties(orderPay, AuthOrderInfoResultResp.class);
        resp.setTradeSerialNo(tradeSerialNo);
        return Result.success(resp);
    }

    /**
     * 用户中心-订单分页
     *
     * @param req 分页参数
     * @return 交易订单
     */
    public Result<Page<AuthOrderInfoPageResp>> page(AuthOrderInfoPageReq req) {
        // 处理查询条件
        LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getUserId, ThreadContext.getUserId())
                .eq(ObjectUtil.isNotEmpty(req.getOrderStatus()), OrderInfo::getOrderStatus, req.getOrderStatus());

        // 排序
        wrapper.orderByDesc(OrderInfo::getId);

        // 分页查询
        IPage<OrderInfo> page = dao.page(PageDTO.of(req.getPageCurrent(), req.getPageSize()), wrapper);
        Page<AuthOrderInfoPageResp> resp = PageUtil.transform(page, AuthOrderInfoPageResp.class);
        if (CollUtil.isNotEmpty(resp.getList())) {

            // 获取试卷信息
            List<Long> examIds = resp.getList().stream().filter(item -> item.getGoodsType().equals(GoodsTypeEnum.EXAM.getCode())).map(AuthOrderInfoPageResp::getGoodsId).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(examIds)) {
                Map<Long, Exam> examRespMap = examDao.listByIds(examIds).stream().collect(Collectors.toMap(Exam::getId, item -> item));
                for (AuthOrderInfoPageResp res : resp.getList()) {
                    Exam examResp = examRespMap.get(res.getGoodsId());
                    if (ObjectUtil.isNotNull(examResp)) {
                        res.setExamType(examResp.getExamType());
                        res.setExamName(examResp.getExamName());
                    }
                }
            }
        }
        return Result.success(resp);
    }
}
